#!/usr/local/bin/perl
# Yen-Ming Lee <leeym@leeym.com>
# $Id$
use FindBin qw($Bin $Script);
use File::Basename;
use Data::Dumper;
use Digest::MD5 qw(md5_hex);
use Storable qw(nstore retrieve);
use strict;

my @D;
my @l;
my @L;
my @I;

my $file;
my $E = 0;
my %db;
my $workdir = $ENV{HOME} . "/.cctidy";
my $dbfile  = "$workdir/md5";
my $verbose = defined($ENV{'DEVTIDY_VERBOSE'});
my $debug   = defined($ENV{'DEVTIDY_DEBUG'});

if (-f $dbfile)
{
  my $ref = retrieve($dbfile);
  %db = %$ref;
}

foreach my $argv (@ARGV)
{
  push (@D, $1) if $argv =~ m{^(-D\S+)};
  push (@I, $1) if $argv =~ m{^(-I\S+)};
  push (@L, $1) if $argv =~ m{^(-L\S+)};
  push (@l, $1) if $argv =~ m{^(-l\S+)};
  $E = 1 if $argv =~ m{^-E};
  $file = $argv if -f $argv && $argv =~ m{\.[ch]\w*$}i;
}

my $cc = shift if ($Script eq 'cctidy' && -x $ARGV[0]);
if (!$cc)
{
  $cc = ($file =~ m{\.c$}) ? 'gcc' : 'g++';
  foreach my $path (split (':', $ENV{PATH}))
  {
    next if $path eq $Bin;
    next if -l "$path/$cc";
    next if (!-x "$path/$cc");
    $cc = "$path/$cc";
    last;
  }
}
die if !$cc;

my $arg = join (' ', @D, @I, @L, @l);
my $cmd = join (' ', $cc, @ARGV);

#$arg =~ s{"}{\\"}g;
#$cmd =~ s{"}{\\"}g;

scan($file) if ($file && !$E);
unlink(<_src_hdrtest.*>);
unlink('a.out');
nstore \%db, $dbfile;
print "EXEC: $cmd\n" if $verbose;
exec($cmd);

# END

sub scan
{
  my $file = shift;
  print "scan($file)\n" if $verbose;
  my %HEADERS = headers($file);
  my %DEPENDS = depends($file);
  print "headers:\n" . Dumper(\%HEADERS) if $debug;
  print "depends:\n" . Dumper(\%DEPENDS) if $debug;
  foreach my $header (keys %HEADERS)
  {

    #print "$header => " . $DEPENDS{$header} . "\n";
    next if !$DEPENDS{$header};
    next if $DEPENDS{$header} =~ m{^(/usr|/usr/local|/home/y)/};
    scan($DEPENDS{$header});
  }
  foreach my $header (keys %HEADERS)
  {

    #print "$header => " . $DEPENDS{$header} . "\n";
    next if !$DEPENDS{$header};
    check($file, $header);
  }
  $db{ md5_hex($file) } = time();
}

sub headers
{
  my $file = shift;
  print "headers($file)\n" if $verbose;
  my %HDR;
  open(F, $file) || die "failed to open $file: $!";
  while (<F>)
  {
    my $line = $_;
    chomp($line);
    $HDR{$2} = "$1$2$3" if $line =~ m{^#include\s+("|<)([\w\.]+)("|>)};
  }
  close(F);
  return %HDR;
}

sub check
{
  my $file   = shift;
  my $header = shift;
  my $w;
  print "check($file, $header)\n" if $verbose;
  my $ext = $1 if $file =~ m{\.(\w+)$};
  my $tmp     = "_src_hdrtest.$ext";
  my $_header = $file;
  $_header =~ s{\.$ext}{\.h};
  open(F, $file) || die $!;
  open(T, ">$tmp") || die $!;

  while (<F>)
  {
    my $line = $_;
    next if $line =~ m{^#include\s+(?:["<])$header(?:[">])};
    print T $line;
  }
  close(F);
  close(T);
  if ($ext =~ m{^h}i)
  {
    die if $ext eq 'C';
    my $tmp2 = "_src_hdrtest.C";
    open(F, ">$tmp2") || die $!;
    print F "#include \"$tmp\"\n";
    print F "int main () { return 0; }\n";
    close(F);
    $tmp = $tmp2;
  }
  my $cmd = "$cc $arg $tmp -W -Wall -Werror -MD -c";
  my $err = System("$cmd > /dev/null 2>&1");
  my $dup = found($header, "_src_hdrtest.d");

  #warn "\n\t$file might not require $header\n" if (!$err && $dup);
  $w = "\nWARNING:\n\t$file does not require $header\n\n" if (!$err && !$dup);
  unlink(<_src_hdrtest.*>);
  unlink('a.out');
  next if $header eq $_header;
  ($ENV{'DEVTIDY_FORCE'} ? warn $w : die $w) if $w;
}

sub found
{
  my $header = shift;
  my $depend = shift;
  open(F, $depend) || die "failed to open $depend: $!";
  my $l = 0;
  while (<F>)
  {
    my $line = $_;
    chomp($line);
    $line =~ s{^\S+:}{} if !$l;
    return 1 if $line =~ m{\b$header\b};
    $l++;
  }
  close(F);
  return 0;
}

sub depends
{
  my $file = shift;
  (my $dep = $file) =~ s{\.\w+$}{\.d};
  print "depends($file)\n" if $verbose;
  if ($file =~ m{\.c\w*$}i)
  {
    System("$cc $arg $file -MD -E > /dev/null");
  }
  else
  {
    my $tmp = '_src_hdrtest.C';
    open(F, ">$tmp") || die $!;
    print F "#include \"$file\"\n";
    print F "int main () { return 0; }\n";
    close(F);
    System("$cc $arg $tmp -MD -E > /dev/null");
    rename('_src_hdrtest.d', $dep);
  }
  open(F, $dep) || die "$Script: can't read $dep: $!";    # return;
  my $l = 0;
  my $depend;
  my %DEP;
  while (<F>)
  {
    my $line = $_;
    chomp($line);
    $line =~ s{^\S+:}{} if !$l;
    $line =~ s{\\$}{};
    $l++;
    $depend .= $line;
  }
  close(F);
  unlink($dep);
  while ($depend =~ m{((?:\S*/)?(\S+))})
  {
    my $path = $1;
    my $file = $2;
    $depend = $';
    $DEP{$file} = $path;
  }
  return %DEP;
}

sub System
{
  my $cmd = shift;
  print "SYSTEM: $cmd\n" if $verbose;
  return system $cmd;
}
